/*
 * Class NativeUtils is published under the The MIT License:
 *
 * Copyright (c) 2012 Adam Heinrich <adam@adamh.cz>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.invesdwin.scripting.julia.runtime.julia4j.internal;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.ProviderNotFoundException;
import java.nio.file.StandardCopyOption;
import java.util.List;

import javax.annotation.concurrent.NotThreadSafe;

import de.invesdwin.context.ContextProperties;
import de.invesdwin.util.collections.Collections;

/**
 * A simple library class which helps with loading dynamic libraries stored in the JAR archive. These libraries usually
 * contain implementation of some methods in native code (using JNI - Java Native Interface).
 *
 * @see <a href=
 *      "http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar">http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar</a>
 * @see <a href="https://github.com/adamheinrich/native-utils">https://github.com/adamheinrich/native-utils</a>
 *
 */
@NotThreadSafe
//CHECKSTYLE:OFF
public final class ModifiedNativeUtils {

    /**
     * The minimum length a prefix for a file has to have according to {@link File#createTempFile(String, String)}}.
     */
    private static final int MIN_PREFIX_LENGTH = 3;
    public static final String NATIVE_FOLDER_PATH_PREFIX = "nativeutils";
    public static final String JAVA_LIBRARY_PATH = "java.library.path";

    /**
     * Temporary directory which will contain the DLLs.
     */
    private static File temporaryDir;

    /**
     * Private constructor - this class will never be instanced
     */
    private ModifiedNativeUtils() {}

    /**
     * Loads library from current JAR archive
     *
     * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after
     * exiting. Method uses String as filename because the pathname is "abstract", not system-dependent.
     *
     * @param path
     *            The path of file inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext
     * @throws IOException
     *             If temporary file creation or read/write operation fails
     * @throws IllegalArgumentException
     *             If source file (param path) does not exist
     * @throws IllegalArgumentException
     *             If the path is not absolute or if the filename is shorter than three characters (restriction of
     *             {@link File#createTempFile(java.lang.String, java.lang.String)}).
     * @throws FileNotFoundException
     *             If the file could not be found inside the JAR.
     */
    public static void loadLibraryFromJar(final String path) throws IOException {

        if (null == path || !path.startsWith("/")) {
            throw new IllegalArgumentException("The path has to be absolute (start with '/').");
        }

        // Obtain filename from path
        final String[] parts = path.split("/");
        final String filename = (parts.length > 1) ? parts[parts.length - 1] : null;

        // Check if the filename is okay
        if (filename == null || filename.length() < MIN_PREFIX_LENGTH) {
            throw new IllegalArgumentException("The filename has to be at least 3 characters long.");
        }

        // Prepare temporary file
        if (temporaryDir == null) {
            temporaryDir = createTempDirectory(NATIVE_FOLDER_PATH_PREFIX);
            temporaryDir.deleteOnExit();
        }

        final File temp = new File(temporaryDir, filename);

        try (InputStream is = ModifiedNativeUtils.class.getResourceAsStream(path)) {
            java.nio.file.Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);
        } catch (final IOException e) {
            temp.delete();
            throw e;
        } catch (final NullPointerException e) {
            temp.delete();
            throw new FileNotFoundException("File " + path + " was not found inside JAR.");
        }

        try {
            System.load(temp.getAbsolutePath());
        } finally {
            if (isPosixCompliant()) {
                // Assume POSIX compliant file system, can be deleted after loading
                temp.delete();
            } else {
                // Assume non-POSIX, and don't delete until last file descriptor closed
                temp.deleteOnExit();
            }
        }
    }

    private static boolean isPosixCompliant() {
        try {
            return FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
        } catch (FileSystemNotFoundException | ProviderNotFoundException | SecurityException e) {
            return false;
        }
    }

    private static File createTempDirectory(final String prefix) throws IOException {
        final String tempDir = ContextProperties.TEMP_DIRECTORY.getAbsolutePath();
        final File generatedDir = new File(tempDir, prefix + System.nanoTime());

        if (!generatedDir.mkdir()) {
            throw new IOException("Failed to create temp directory " + generatedDir.getName());
        }

        return generatedDir;
    }

    public static String libnameToPlatform(final String libname) {

        String path = "/native/";
        if (System.getProperty("os.arch").toLowerCase().endsWith("64")) {
            path += "64/";
        } else {
            throw new IllegalArgumentException("Not support this arch");
        }
        final String osName = System.getProperty("os.name").toLowerCase();
        if (osName.startsWith("mac os")) {
            path += "darwin/" + libname + ".jnilib";
        } else if (osName.startsWith("linux")) {
            path += "linux/" + libname + ".so";
        } else if (osName.startsWith("windows")) {
            path += "windows/" + libname + ".dll";
        } else {
            throw new IllegalArgumentException("Not support this OS");
        }

        return path;
    }

    /**
     * Debug method
     *
     * @return list of names of loaded dynamic libraries
     */
    @SuppressWarnings("unchecked")
    public static List<String> loadedLibraryNames() {
        try {
            final Field lib = ClassLoader.class.getDeclaredField("loadedLibraryNames");
            lib.setAccessible(true);
            final Object list = lib.get(ClassLoader.getSystemClassLoader());
            if (list instanceof List<?>) {
                return (List<String>) list;
            }
        } catch (IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

}
